function Graphics(canvas){
    this.context2d = canvas.getContext("2d");
    this.canvas = canvas;
	this.filters = [];
}

Graphics.prototype.getWidth = function(){
    return this.canvas.width;
}

Graphics.prototype.getHeight = function(){
    return this.canvas.height;
}

Graphics.prototype.save = function(){
    this.context2d.save();
}

Graphics.prototype.restore = function(){
    this.context2d.restore();
}

Graphics.prototype.scale = function(x, y){
    this.context2d.scale(x, y);
}

Graphics.prototype.rotate = function(angle){
    this.context2d.rotate(angle);
}

Graphics.prototype.translate = function(x, y){
    this.context2d.translate(x, y);
}

Graphics.prototype.transform = function(m11, m12, m21, m22, dx, dy){
    this.context2d.transform(m11, m12, m21, m22, dx, dy);
}

Graphics.prototype.drawImage = function(image, sx, sy, sw, sh, dx, dy, dw, dh){
    this.context2d.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
}

Graphics.prototype.setStrokeStyle = function(style){
    this.context2d.strokeStyle = style;
}

Graphics.prototype.setFillStyle = function(style){
    this.context2d.fillStyle = style;
}

Graphics.prototype.setShadowOffset = function(x, y){
    this.context2d.shadowOffsetX = x;
    this.context2d.shadowOffsetY = y;
}
Graphics.prototype.setShadowOffsetX = function(x){
    this.context2d.shadowOffsetX = x;
}
Graphics.prototype.setShadowOffsetY = function(y){
    this.context2d.shadowOffsetY = y;
}

Graphics.prototype.setShadowBlur = function(amount){
    this.context2d.shadowBlur = amount;
}

Graphics.prototype.setShadowColor = function(color){
    this.context2d.shadowColor = colort;
}

Graphics.prototype.setAlpha = function(value){
    this.context2d.globalAlpha = value;
}

Graphics.prototype.setCompositeOperation = function(operation){
    this.context2d.globalCompositeOperation = operation;
}

Graphics.prototype.setLineWidth = function(width){
    this.context2d.lineWidth = width;
}

Graphics.prototype.setLineCapStyle = function(style){
    this.context2d.lineCap = style;
}

Graphics.prototype.setLineJoinStyle = function(style){
    this.context2d.lineJoin = style;
}

Graphics.prototype.setLineMiterLimit = function(limit){
    this.context2d.miterLimit = limit;
}

Graphics.prototype.createLinearGradient = function(x0, y0, x1, y1, colorStops){
    var gradient = this.context2d.createLinearGradient(x0, y0, x1, y1);
    for(var i = 0; i < colorStops.length; i++){
        var stop  = colorStops[i].stop;
        var color = colorStops[i].color;
        gradient.addColorStop(stop, color);
    }
    return gradient;
}

Graphics.prototype.createRadialGradient = function(x0, y0, x1, y1, r, colorStops){
    var gradient = this.context2d.createRadialGradient(x0, y0, x1, y1, r);
    for(var i = 0; i < colorStops.length; i++){
        var stop  = colorStops[i].stop;
        var color = colorStops[i].color;
        gradient.addColorStop(stop, color);
    }
    return gradient;
}

Graphics.prototype.createImagePattern = function(image, repetition){
    return this.context2d.createPattern(image, repetition);
}

Graphics.prototype.fillRect = function(x, y, width, height){
    this.context2d.fillRect(x, y, width, height);
}

Graphics.prototype.strokeRect = function(x, y, width, height){
    this.context2d.strokeRect(x, y, width, height);
}

Graphics.prototype.fillCircle = function(x, y, radius){
    this.context2d.beginPath();
    this.context2d.arc(x, y, radius, 0, 2 * Math.PI, false);
    this.context2d.fill();
}

Graphics.prototype.strokeCircle = function(x, y, radius){
    this.context2d.beginPath();
    this.context2d.arc(x, y, radius, 0, 2 * Math.PI, false);
    this.context2d.stroke();
}

Graphics.prototype.clear = function(){
    this.context2d.clearRect(0, 0, this.getWidth(), this.getHeight());
}

// TODO: implement paths
Graphics.prototype.beginPath = function(){
	this.context2d.beginPath();	
}

Graphics.prototype.moveTo = function(x, y){
	this.context2d.moveTo(x,y);	
}

Graphics.prototype.lineTo = function(x, y){
	this.context2d.lineTo(x,y);	
}

Graphics.prototype.quadraticCurveTo = function(x0, y0, x1, y1){
	this.context2d.quadraticCurveTo(x0, y0, x1, y1);	
}

Graphics.prototype.bezierCurveTo = function(x0, y0, x1, y1, x2, y2){
	this.context2d.bezierCurveTo(x0, y0, x1, y1, x2, y2);	
}

Graphics.prototype.curveTo = function(x0, y0, x1, y1, x2, y2){
	arguments.length == 4 ? this.context2d.quadraticCurveTo(x0, y0, x1, y1) : this.context2d.bezierCurveTo(x0, y0, x1, y1, x2, y2);	
}

Graphics.prototype.closePath = function(){
	this.context2d.closePath();	
}

Graphics.prototype.stroke = function(){
	this.context2d.stroke();	
}



// TODO: implement text



// TODO: implement pixel manipulation
Graphics.prototype.getImageData = function(){
	return this.context2d.getImageData(0,0,this.canvas.width, this.canvas.height);
}
Graphics.prototype.putImageData = function(imageData){
	this.context2d.putImageData(imageData, 0, 0);
}
Graphics.prototype.setPixel = function(x, y, r, g, b, a){
	var imageData = this.context2d.getImageData(0,0,this.canvas.width,this.canvas.height);
	var index = (x + y * imageData.width)*4;
	imageData.data[index+0] = r;
	imageData.data[index+1] = g;
	imageData.data[index+2] = b; 
	imageData.data[index+3] = a;
}
Graphics.prototype.setPixelRed = function(x, y, r){
	var imageData = this.context2d.getImageData(0,0,this.canvas.width,this.canvas.height);
	imageData.data[(x + y * imageData.width)*4+0] = r;
}
Graphics.prototype.setPixelGreen = function(x, y, g){
	var imageData = this.context2d.getImageData(0,0,this.canvas.width,this.canvas.height);
	imageData.data[(x + y * imageData.width)*4+1] = g;
}
Graphics.prototype.setPixelBlue = function(x, y, b){
	var imageData = this.context2d.getImageData(0,0,this.canvas.width,this.canvas.height);
	imageData.data[(x + y * imageData.width)*4+2] = b;
}
Graphics.prototype.setPixelAlpha = function(x, y, a){
	var imageData = this.context2d.getImageData(0,0,this.canvas.width,this.canvas.height);
	imageData.data[(x + y * imageData.width)*4+3] = a;
}
Graphics.prototype.getPixel = function(x, y){
	var imageData = this.context2d.getImageData(0,0,this.canvas.width,this.canvas.height);
	var index = (x + y * imageData.width)*4;
	return [imageData.data[index+0], imageData.data[index+1], imageData.data[index+2], imageData.data[index+3]];
}
Graphics.prototype.getPixelRed = function(x, y){
	var imageData = this.context2d.getImageData(0,0,this.canvas.width,this.canvas.height);
	return imageData.data[(x + y * imageData.width)*4+0];
}
Graphics.prototype.getPixelGreen = function(x, y){
	var imageData = this.context2d.getImageData(0,0,this.canvas.width,this.canvas.height);
	return imageData.data[(x + y * imageData.width)*4+1];
}
Graphics.prototype.getPixelBlue = function(x, y){
	var imageData = this.context2d.getImageData(0,0,this.canvas.width,this.canvas.height);
	return imageData.data[(x + y * imageData.width)*4+2];
}
Graphics.prototype.getPixelAlpha = function(x, y){
	var imageData = this.context2d.getImageData(0,0,this.canvas.width,this.canvas.height);
	return imageData.data[(x + y * imageData.width)*4+3];
}

Graphics.prototype.addFilter = function(filter){
	this.filters.push(filter);
}
Graphics.prototype.removeFilter = function(filter){
	this.filters.remove(filter);
}
Graphics.prototype.applyFilters = function(){
	var newData = this.getImageData();
	var l = this.filters.length;
	var e = l-1;
	while(l--){
		newData = this.filters[e-l].use(newData);
	}
	this.context2d.putImageData(newData, 0, 0);
}


Filter = function(args){}
Filter.prototype.use = function(pixels){};


Filter.extend('ConvolutionFilter');
ConvolutionFilter = function(args){
    this.opaque = args.opaque || false;
	this.weights = args.weights || [1];
	this.filterSides = Math.round(Math.sqrt(this.weights.length));
	this.filterSideHalf = Math.floor(this.filterSides/2);
	this.divisor = args.divisor || (this.weights.reduce(function(a, b) {return a + b;}) || 1);
}
ConvolutionFilter.prototype.use = function(pixels){
	var filterSides = this.filterSides;
	var filterSideHalf = this.filterSideHalf
	var divisor = this.divisor;
	var src = pixels.data;
	var sourceWidth = pixels.width;
	var sourceHeight = pixels.height;
	var output = pixels;
	var destination = output.data;
	var alphaFactor = this.opaque;
	var y = sourceHeight;
	while(y--){
		var x = sourceWidth;
		while(x--){
			var offset = (y*sourceWidth + x)*4;
			var r = 0; var g = 0; var b = 0; var a = 0;
			var convolutionX = filterSides;
			while(convolutionX--){
				var convolutionY = filterSides;
				while(convolutionY--){
					var sourceConvolutedX = Math.min(sourceWidth - 1, Math.max(0, x + convolutionX - filterSideHalf));
					var sourceConvolutedY = Math.min(sourceHeight- 1, Math.max(0, y + convolutionY - filterSideHalf));	
					var sourceOffset = (sourceConvolutedY*sourceWidth + sourceConvolutedX)*4;
					var wt = this.weights[convolutionY*filterSides + convolutionX];
					if(src[sourceOffset+3] == 0){
						src[sourceOffset+0] = 255;
						src[sourceOffset+1] = 255;
						src[sourceOffset+2] = 255;
						src[sourceOffset+3] = 0;
					}
					r += src[sourceOffset+0] * wt;
					g += src[sourceOffset+1] * wt;
					b += src[sourceOffset+2] * wt;
					a += src[sourceOffset+3] * wt;
				}
			}
			destination[offset+0] = r	/	divisor;
			destination[offset+1] = g	/	divisor;
			destination[offset+2] = b	/	divisor;
			destination[offset+3] = (a + alphaFactor*(255-a)) / divisor  ;
		}
	}
	return output;
}


Filter.extend('BlurFilter');
BlurFilter = function(args){
	var a = {};
	this.weights = [];
	this.divisor = args.divisor;
	this.quadratic = args.quadratic || false;
	this.amount = args.amount || 1;
	
	var range = args.amount*2+1 || 3;
	var rq = range*range;
	var qb = rq;
	
	if(this.quadratic){
		while(qb--){
			this.weights.push(1/range);
		}			
	}else{
		while(qb--){
			this.weights.push( range / (Math.abs(args.amount - qb % range)+1) * range/(Math.abs(args.amount - ((qb - qb % range) ) / range)+1));
		}		
	}
	this.convolutionFilter = new ConvolutionFilter({opaque:false, weights:this.weights, divisor:this.divisor });
}

BlurFilter.prototype.use = function(pixels){
	return this.convolutionFilter.use(pixels);
}


Filter.extend('GreyscaleFilter');
GreyscaleFilter = function(args){
	
}
GreyscaleFilter.prototype.use = function(pixels){
	var d = pixels.data;
	var l = d.length/4;
	while(l--){
	 	var i = l*4;
		var r = d[i];
		var g = d[i+1];
		var b = d[i+2];
		var v = 0.2126*r + 0.7152*g + 0.0722*b;
		d[i] = d[i+1] = d[i+2] = v
	}
	return pixels;
	
}


Filter.extend('InvertFilter');
InvertFilter = function(args){}
InvertFilter.prototype.use = function(pixels){
	var d = pixels.data;
	var l = d.length/4;
	while(l--){
	 	var i = l*4;
		d[i] = 255-d[i];
		d[i+1] = 255-d[i+1];
		d[i+2] = 255-d[i+2];
	}
	return pixels;
}


Filter.extend('ChannelFilter');
ChannelFilter = function(args){
	this.offset = 3;
	switch (args.channel){
		case 'red':
			this.offset = 0;
		break;
		case 'green':
			this.offset = 1
		break;
		case 'blue':
			this.offset = 2;
		break;	
		case 'alpha':
			this.offset = 3;
		break;
		default:
			this.offset = 3;
		break;
	}
}
ChannelFilter.prototype.use = function(pixels){
	var d = pixels.data;
	var l = d.length/4;
	while(l--){
		var i = l*4;
		d[i] = d[i+1] = d[i+2] = d[i+this.offset];
	}
}


Filter.extend('ThresholdFilter');
ThresholdFilter = function(args){this.threshold = args.threshold;}
ThresholdFilter.prototype.use = function(pixels){
	var d = pixels.data;
	var l = d.length/4;
	while(l--){
	 	var i = l*4;
		var v = (0.2126*d[i] + 0.7152*d[i+1] + d[i+2] >= this.treshold) ? 255 : 0;
		d[i] = d[i+1] = d[i+2] = v
	}
  return pixels;
}


Filter.extend('SharpenFilter');
SharpenFilter = function(args){
	this.weights = [];
	this.divisor = args.divisor || 1;
	this.amount = Math.round(args.amount/2)*2 + 1 || 3;
	this.side = (this.amount - 1) / 2
	this.quadratic = this.amount*this.amount;
	this.middle = (this.quadratic +1)/2 -1;
	var qb = this.quadratic;
	var sum = 0;
	while(qb--){
		var val = 0;
		var x = qb%this.amount;
		var y = (qb-x)/this.amount
		var dx = Math.abs(this.side - x);
		var dy = Math.abs(this.side - y);
		var mx = 1;
		var my = 1;
		var cx = dx
		while(cx--){
			mx /=2;
		}				
		var cy = dy;
		while(cy--){
			my /=2;
		}	
		val = mx*my;
		this.weights.push(-val);
		sum += val;
	}
	
	this.weights[this.middle] = sum +1;
	this.convolutionFilter = new ConvolutionFilter({opaque:false, weights: this.weights});
}
SharpenFilter.prototype.use = function(pixels){
	return this.convolutionFilter.use(pixels);
}


